#include "apue.h"
#include <stdlib.h>
#include <pthread.h>

extern struct queue my_queue;

void test_job(pthread_t *, pthread_t *, pthread_t *, pthread_t *, struct queue *);

struct job {
    struct job *j_next;
    struct job *j_prev;
    pthread_t j_id; /* tells which thread handles this job */
    int j_no;
};

struct queue {
    struct job *q_head;
    struct job *q_tail;
    pthread_rwlock_t q_lock;
};

// static pthread_barrier_t ready_b;
static int has_ready= 0;
static pthread_mutex_t ready_lock= PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t ready_cond= PTHREAD_COND_INITIALIZER;

int queue_init(struct queue *qp) {
    int err;
    qp->q_head= NULL;
    qp->q_tail= NULL;
    if ((err= pthread_rwlock_init(&qp->q_lock, NULL)) != 0)
        err_exit(err, "pthread_rwlock_init error");
    /* ... continue initialization ... */
    return 0;
}

/* insert job at the head of queue*/
void job_insert(struct queue *qp, struct job *jp) {
    pthread_rwlock_wrlock(&qp->q_lock);
    jp->j_next= qp->q_head;
    jp->j_prev= NULL;
    if (qp->q_head != NULL)
        qp->q_head->j_prev= jp;
    else 
        qp->q_tail= jp; /* qp is empty */
    qp->q_head= jp; 
    pthread_rwlock_unlock(&qp->q_lock);
}

/* Append a job on the tail of the queue */
void job_append(struct queue *qp, struct job *jp) {
    pthread_rwlock_wrlock(&qp->q_lock);
    jp->j_next= NULL;
    jp->j_prev= qp->q_tail;
    if (qp->q_tail != NULL) 
        qp->q_tail->j_next= jp;
    else
        qp->q_head= jp;
    qp->q_tail= jp;
    pthread_rwlock_unlock(&qp->q_lock);
}

void job_remove(struct queue *qp, struct job *jp) {
    pthread_rwlock_wrlock(&qp->q_lock);
    //if (jp == qp->q_head) {   // at first
    //    qp->q_head= jp->j_next;
    //    if (qp->q_tail== jp)  // at first and qp length = 1
    //        qp->q_tail= NULL; 
    //    else 
    //        jp->j_next->j_prev= NULL;

    //} else if (jp == qp->q_tail) {    // at end and qp length > 1
    //    jp->j_prev->j_next= NULL;
    //    qp->q_tail= jp->j_prev;

    //} else {
    //    jp->j_prev->j_next= jp->j_next;
    //    jp->j_next->j_prev= jp->j_prev;
    //}
    // or write next way

    if (jp->j_next)
        jp->j_next->j_prev= jp->j_prev;  
    if (jp->j_prev)
        jp->j_prev->j_next= jp->j_next;
    if (jp == qp->q_head)
        qp->q_head= jp->j_next;
    if (jp == qp->q_tail)
        qp->q_tail= jp->j_prev;
    pthread_rwlock_unlock(&qp->q_lock);
}

struct job * job_find(struct queue *qp, pthread_t id) {
    int err;
    if ((err= pthread_rwlock_rdlock(&qp->q_lock)))
        err_exit(err, "pthread_rwlock_rdlock error");
        
    struct job * jp = qp->q_head;
    while (jp) {
        if (pthread_equal(jp->j_id, id))
            break; 
        jp= jp->j_next;
    }
    pthread_rwlock_unlock(&qp->q_lock);
    return jp;
}


void * thr_fn(void *arg) {
    int i=0;
    unsigned long thr_no = (unsigned long)arg;
    pthread_t id= pthread_self();
    struct job *job;

    pthread_mutex_lock(&ready_lock);
    while (has_ready == 0)
        pthread_cond_wait(&ready_cond, &ready_lock);
    pthread_mutex_unlock(&ready_lock);

    while ((job = job_find(&my_queue, id))) {
        printf("#%lu get job %d.\n", thr_no, job->j_no);
        job_remove(&my_queue, job);
        ++i;
    }

    printf("#%lu total get %d jobs\n", thr_no, i);
    return (void *)0; 
}

struct queue my_queue;

int main(void) {
    pthread_t pt1, pt2, pt3, pt4;
    int err;
    struct job;

    queue_init(&my_queue);

    //pthread_barrier_init(&ready_b, NULL, 5);
    
    if ((err= pthread_create(&pt1, NULL, thr_fn, (void *)0)))
        err_exit(err, "pthread_create pt1 error");
    if ((err= pthread_create(&pt2, NULL, thr_fn, (void *)1)))
        err_exit(err, "pthread_create pt2 error");
    if ((err= pthread_create(&pt3, NULL, thr_fn, (void *)2)))
        err_exit(err, "pthread_create pt3 error");
    if ((err= pthread_create(&pt4, NULL, thr_fn, (void *)3)))
        err_exit(err, "pthread_create pt4 error");

    test_job(&pt1, &pt2, &pt3, &pt4, &my_queue);
    has_ready= 1;
    pthread_cond_broadcast(&ready_cond);
    //pthread_barrier_wait(&ready_b);
    //pthread_barrier_destroy(&ready_b);
    

    if (pthread_join(pt1, NULL))
        err_exit(err, "pthread join pt1 error");
    if (pthread_join(pt2, NULL))
        err_exit(err, "pthread join pt2 error");
    if (pthread_join(pt3, NULL))
        err_exit(err, "pthread join pt3 error");
    if (pthread_join(pt4, NULL))
        err_exit(err, "pthread join pt4 error");

    printf("end\n");
}

void test_job(pthread_t * pt1, pthread_t * pt2, pthread_t * pt3, pthread_t * pt4, struct queue *qp){
    int i;
    struct job * job;
    pthread_t * pt;
    for (i= 0; i< 100; ++i) {
        switch (i % 4) {
            case 0: pt= pt1; break;
            case 1: pt= pt2; break;
            case 2: pt= pt3; break;
            case 3: pt= pt4; break;
        }
        job= malloc(sizeof(struct job));
        job->j_no= i;
        job->j_id= *pt;
        job_insert(qp, job);
    }
}


